package ch.subgate.spritestudio.widgets;

import ch.subgate.spritestudio.constants.Constants;
import ch.subgate.spritestudio.tools.ImageProcessing;
import ch.subgate.spritestudio.types.PaintEvent;
import ch.subgate.spritestudio.types.Sprite;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class PainterView extends SurfaceView implements SurfaceHolder.Callback {
	public class PainterThread extends Thread {
		
		/** SurfaceHolder gives access to the canvas */
		private SurfaceHolder mSurfaceHolder;
		
		/** Variable to allow pausing and resuming the threads execution */
		private boolean mIsPaused;
		
		/** Variable to stop the threads execution */
		private boolean mIsRunning;
		
		/** Reference to the currently edited sprite */
		private Sprite mSprite;
		
		private Bitmap mBackground;
		
		/** Paint variable to be used at various points */
		private Paint mPaint;
		
		/** RectF variable to be used at various points */
		private RectF mRect;
		
		/*
		 * Variables to store the surface dimensions
		 */
		/** Surface width */
		private int mSurfaceWidth = 1;
		
		/** Surface height */
		private int mSurfaceHeight = 1;
		
		/*
		 * Variables to store the viewing and the snapped position
		 */
		/** Viewing x position, describes the positioning of the center of image on the screen */
		private float mViewX;
		
		/** Viewing y position, describes the positioning of the center of image on the screen */
		private float mViewY;
		
		/** Current zoom level */
		private float mCurrentZoom;
		
		/** Delta X when moving the picture */
		private float mDX;
		
		/** Delta Y when moving the picture */
		private float mDY;
		
		/** Delta Zoom */
		private float mDZoom;
		
		/** Variable holds the value of the current sprite frame */
		private int mCurrentFrame;
		
		public PainterThread(SurfaceHolder surfaceHolder, Sprite sprite) {
			//saves the handles for the SurfaceHolder and Context
			mSurfaceHolder = surfaceHolder;
			
			//pausing the thread on initialization
			mIsRunning = false;
			mIsPaused = true;
			
			mSprite = sprite;
			
			//Create a paint object for later use
			mPaint = new Paint();
			mPaint.setAntiAlias(false);
			mRect = new RectF();
			
			// Prepare background
			mBackground = Bitmap.createBitmap(
					mSurfaceWidth,
					mSurfaceHeight,
					Bitmap.Config.ARGB_8888);
			Canvas canvas = new Canvas(mBackground);
			drawBackground(canvas);
			
			//set the default viewing position
			mViewX = 0.0f;
			mViewY = 0.0f;
			mCurrentFrame = 0;
			
			//Set the zoom level to default
			mCurrentZoom = Constants.DEFAULT_ZOOM;
			
			//Initialize the positioning delta values
			mDX = 0.0f;
			mDY = 0.0f;
			mDZoom = 1.0f;
		}
		
		/**
		 * setter method for threads running state
		 */
		public void setRunning(boolean running) {
			mIsRunning = running;
		}
		
		/**
		 * setter method for threads pausing state
		 */
		public void setPaused(boolean paused) {
			mIsPaused = paused;
		}
		
		/**
		 * Standard override for the run method for the drawing calls
		 */
		@Override
		public void run() {
			Canvas canvas;
			while (mIsRunning) {
				if (!mIsPaused) {
					canvas = null;
					try {
						canvas = mSurfaceHolder.lockCanvas();
						synchronized (mSurfaceHolder) {
							draw(canvas);
						}
					}
					catch (Exception e) {
						// TODO
					}
					finally {
						if (canvas != null) {
							mSurfaceHolder.unlockCanvasAndPost(canvas);
						}
					}
				}
			}
		}
		
		/**
		 * Method holding all the drawing calls
		 */
		public void draw(Canvas canvas) {
			// Draw the background pattern
			if (mBackground != null && !mBackground.isRecycled()) {
				if (!mBackground.isRecycled()) {
					canvas.drawBitmap(mBackground, 0.0f, 0.0f, null);
				}
			}
			
			// Draw the currently edited image
			float left = this.getImageLeft();
			float top = this.getImageTop();
			mRect.set(
					left,
					top,
					left + (float)mSprite.mFrameWidth * mCurrentZoom * mDZoom,
					top + (float)mSprite.mHeight * mCurrentZoom * mDZoom);
			mPaint.setColor(0x66ffffff);
			canvas.drawRect(mRect, mPaint);
			mSprite.drawFrame(mCurrentFrame, canvas, mRect);
			
			if (mCurrentZoom * mDZoom > Constants.DETAILED_ZOOM_LIMIT) {
				mPaint.setTextSize(10.0f);
				mPaint.setStrokeWidth(1.0f);
				
				//Draw rulers and help lines
				mPaint.setColor(Color.WHITE);
				mRect.set(0.0f, 0.0f, (float)mSurfaceWidth, Constants.DEFAULT_RULER_THICKNESS);
				canvas.drawRect(mRect, mPaint);
				mRect.set(0.0f, 0.0f, Constants.DEFAULT_RULER_THICKNESS, (float)mSurfaceHeight);
				canvas.drawRect(mRect, mPaint);
				
				//Draw vertical grid lines
				mPaint.setColor(Color.GRAY);
				int count = -(int)(left / (mCurrentZoom * mDZoom));
				for (
						float dx = left % (mCurrentZoom * mDZoom);
						dx < mSurfaceWidth;
						dx += mCurrentZoom * mDZoom) {
					if (dx > Constants.DEFAULT_RULER_THICKNESS) {
						if (count == 0 || count == mSprite.getBitmap().getWidth()) {
							mPaint.setStrokeWidth(3.0f);
							mPaint.setColor(Color.BLUE);
						}
						canvas.drawLine(
								dx,
								count % 5 == 0 ? Constants.DEFAULT_RULER_PADDING_L : Constants.DEFAULT_RULER_PADDING_H,
								dx,
								mSurfaceHeight,
								mPaint);
						mPaint.setStrokeWidth(1.0f);
						mPaint.setColor(Color.GRAY);
						canvas.drawText(
								Integer.toString(count),
								dx,
								Constants.DEFAULT_RULER_PADDING_TY,
								mPaint);
					}
					count++;
				}
				//Draw horizontal grid lines
				count = -(int)(top / (mCurrentZoom * mDZoom));
				for (
						float dy = top % (mCurrentZoom * mDZoom);
						dy < mSurfaceHeight;
						dy += mCurrentZoom * mDZoom) {
					if (dy > Constants.DEFAULT_RULER_THICKNESS) {
						if (count == 0 || count == mSprite.getBitmap().getHeight()) {
							mPaint.setStrokeWidth(3.0f);
							mPaint.setColor(Color.BLUE);
						}
						canvas.drawLine(
								count % 5 == 0 ? Constants.DEFAULT_RULER_PADDING_L : Constants.DEFAULT_RULER_PADDING_H,
								dy,
								mSurfaceWidth,
								dy,
								mPaint);
						mPaint.setStrokeWidth(1.0f);
						mPaint.setColor(Color.GRAY);
						canvas.drawText(
								Integer.toString(count),
								Constants.DEFAULT_RULER_PADDING_TX,
								dy,
								mPaint);
					}
					count++;
				}
				
				//Draw ruler separation lines
				mPaint.setColor(Color.BLACK);
				mPaint.setStrokeWidth(2.0f);
				canvas.drawLine(
						Constants.DEFAULT_RULER_THICKNESS,
						Constants.DEFAULT_RULER_THICKNESS,
						mSurfaceWidth,
						Constants.DEFAULT_RULER_THICKNESS,
						mPaint);
				canvas.drawLine(
						Constants.DEFAULT_RULER_THICKNESS,
						Constants.DEFAULT_RULER_THICKNESS,
						Constants.DEFAULT_RULER_THICKNESS,
						mSurfaceHeight,
						mPaint);
			}
		}
		
		/**
		 * Transforms the users swipes into picture movement on screen
		 * @param dX deltaX between touchDownX and touchMoveX
		 * @param dY deltaY between touchDownY and touchMoveY
		 */
		public void doMove(float dX, float dY) {
			mDX = dX;
			mDY = dY;
		}
		
		/** Turns on snapping */
		public void doUpdate() {
			mViewX += mDX;
			mViewY += mDY;
			mCurrentZoom *= mDZoom;
			mDX = 0.0f;
			mDY = 0.0f;
			mDZoom = 1.0f;
		}
		
		/** Method to center the picture */
		public void doCenter() {
			mViewX = (mSurfaceWidth - mSprite.mFrameWidth) / 2;
			mViewY = (mSurfaceHeight - mSprite.mHeight) / 2;
		}
		
		public void doZoom(
				float oXa, float oYa, float oXb, float oYb,
				float nXa, float nYa, float nXb, float nYb) {
			float od = FloatMath.sqrt(((oXa - oXb) * (oXa - oXb)) + ((oYa - oYb) * (oYa - oYb)));
			float nd = FloatMath.sqrt(((nXa - nXb) * (nXa - nXb)) + ((nYa - nYb) * (nYa - nYb)));
			mDZoom = nd / od;
			float zoomOriginX = (oXa + oXb) / 2.0f;
			float zoomOriginY = (oYa + oYb) / 2.0f;
			float dx = mViewX - zoomOriginX;
			float dy = mViewY - zoomOriginY;
			float a = (float)Math.atan(dx/dy);
			a += dy < 0 ? Math.PI : 0.0f;
			float c = FloatMath.sqrt((dx * dx) + (dy * dy));
			float dc = (mDZoom - 1.0f) * c;
			mDX = FloatMath.sin(a) * dc;
			mDY = FloatMath.cos(a) * dc;
		}
		
		public PaintEvent performAction(
				float touchX,
				float touchY,
				int color,
				PaintEvent event,
				int tool) {
			float left = (mViewX) - (((float)(mSprite.mFrameWidth) * mCurrentZoom) / 2.0f);
			float top = (mViewY) - (((float)(mSprite.mHeight) * mCurrentZoom) / 2.0f);
			int x = (touchX - left) < 0.0f ? (int)((touchX - left - 1.0f) / mCurrentZoom) : (int)((touchX - left) / mCurrentZoom);
			x += mCurrentFrame * mSprite.mFrameWidth;
			int y = (touchY - top) < 0.0f ? (int)((touchY - top - 1.0f) / mCurrentZoom) : (int)((touchY - top) / mCurrentZoom);
			
			if (x < 0 || x > mSprite.mFrameWidth - 1 || y < 0 || y > mSprite.mHeight - 1) {
				return null;
			}
			PaintEvent resultevent = null;
			int ncolor = 0, ocolor = 0;
			ocolor = mSprite.getBitmap().getPixel(x, y);
			switch (tool) {
			case Constants.TOOL_PENCIL:
				ncolor = ImageProcessing.porterDuffOver(color, ocolor);
				resultevent = ImageProcessing.pencil(mSprite.getBitmap(), x, y, ncolor, ocolor, event);
				break;
			case Constants.TOOL_PAINTBUCKET:
				ncolor = ImageProcessing.porterDuffOver(color, ocolor);
				resultevent = ImageProcessing.paintbucket(
						mSprite.getBitmap(),
						x, y, mCurrentFrame * mSprite.mFrameWidth, ((mCurrentFrame + 1) * mSprite.mFrameWidth) - 1,
						color, ncolor, ocolor, event);
				break;
			case Constants.TOOL_ERASER:
				ncolor = Color.TRANSPARENT;
				resultevent = ImageProcessing.pencil(mSprite.getBitmap(), x, y, ncolor, ocolor, event);
				break;
			}
			if (resultevent != null) {
				resultevent.setApplied(true);
			}
			return resultevent;
		}
		
		/** This method returns the color of a pixel having received a touch event */
		public int getTouchedPixelColor(float touchX, float touchY) {
			if (mSprite.getBitmap() != null) {
				if (!mSprite.getBitmap().isRecycled()) {
					float left = (mViewX) - (((float)(mSprite.mFrameWidth) * mCurrentZoom) / 2.0f);
					float top = (mViewY) - (((float)(mSprite.mHeight) * mCurrentZoom) / 2.0f);
					int x = (touchX - left) < 0.0f ? (int)((touchX - left - 1.0f) / mCurrentZoom) : (int)((touchX - left) / mCurrentZoom);
					x += mCurrentFrame * mSprite.mFrameWidth;
					int y = (touchY - top) < 0.0f ? (int)((touchY - top - 1.0f) / mCurrentZoom) : (int)((touchY - top) / mCurrentZoom);
					if (x >= 0 && x < mSprite.mFrameWidth && y >= 0 && y < mSprite.mHeight) {
						return mSprite.getBitmap().getPixel(x, y);
					}
				}
			}
			return 0;
		}
		
		/**
		 * This method should be used in case of the surface changes its size
		 * @param width New surface width
		 * @param height New surface height
		 */
		public void setSurfaceSize(int width, int height) {
			synchronized (mSurfaceHolder) {
				mSurfaceWidth = width;
				mSurfaceHeight = height;
				mViewX = (float)mSurfaceWidth / 2.0f;
				mViewY = (float)mSurfaceHeight / 2.0f;
				if (mBackground != null) {
					if (!mBackground.isRecycled()) {
						mBackground.recycle();
					}
					mBackground = null;
				}
				mBackground = Bitmap.createBitmap(
						mSurfaceWidth,
						mSurfaceHeight,
						Bitmap.Config.ARGB_8888);
				Canvas canvas = new Canvas(mBackground);
				drawBackground(canvas);
			}
		}
		
		public void freeData() {
			synchronized (mSurfaceHolder) {
				mSurfaceWidth = 0;
				mSurfaceHeight = 0;
				mBackground.recycle();
				mBackground = null;
				mSprite = null;
				mPaint = null;
				mRect = null;
			}
		}
		
		public void drawBackground(Canvas canvas) {
			canvas.drawColor(0xffffffff);
			for (float i = 0.0f; i < (float)canvas.getWidth(); i += Constants.PAINTER_BG_PATTERN_SIZE) {
				for (float j = 0.0f; j < (float)canvas.getHeight(); j += Constants.PAINTER_BG_PATTERN_SIZE) {
					if (((i / Constants.PAINTER_BG_PATTERN_SIZE) + (j / Constants.PAINTER_BG_PATTERN_SIZE)) % 2.0f == 0.0f) {
						mPaint.setColor(0xffeeeeee);
					}
					else {
						mPaint.setColor(0xffcccccc);
					}
					canvas.drawRect(
							i, j,
							i + Constants.PAINTER_BG_PATTERN_SIZE,
							j + Constants.PAINTER_BG_PATTERN_SIZE,
							mPaint);
				}
			}
		}
				
		/**
		 * Standard getter for the sprite
		 */
		public Sprite getSprite() {
			return mSprite;
		}
		
		/**
		 * Standard setter for sprite
		 */
		public void setSprite(Sprite sprite) {
			mSprite = sprite;
		}
		
		/**
		 * Standard setter for viewX and viewY
		 */
		public void setView(float viewX, float viewY) {
			//Set the new viewing coordinates
			mViewX = viewX;
			mViewY = viewY;
		}
		
		/**
		 * Standard getter for mViewX
		 */
		public float getViewX() {
			return mViewX;
		}
		
		/**
		 * Standard getter for mViewY
		 */
		public float getViewY() {
			return mViewY;
		}
		
		public float getImageLeft() {
			return (mViewX + mDX) - (((float)(mSprite.mFrameWidth) * mCurrentZoom * mDZoom) / 2.0f);
		}
		
		public float getImageTop() {
			return (mViewY + mDY) - (((float)(mSprite.mHeight) * mCurrentZoom * mDZoom) / 2.0f);
		}
		
		public float getImageXFromTouch(float touchX) {
			float left = this.getImageLeft();
			return (touchX - left) < 0.0f ? ((touchX - left - 1.0f) / (mCurrentZoom * mDZoom)) : ((touchX - left) / (mCurrentZoom * mDZoom));
		}
		
		public float getImageYFromTouch(float touchY) {
			float top = this.getImageTop();
			return (touchY - top) < 0.0f ? ((touchY - top - 1.0f) / (mCurrentZoom * mDZoom)) : ((touchY - top) / (mCurrentZoom * mDZoom));
		}
		
		@Override
		protected void finalize() throws Throwable {
			super.finalize();
			
			if (mBackground != null) {
				if (!mBackground.isRecycled()) {
					mBackground.recycle();
				}
				mBackground = null;
			}
			
			mSprite = null;
			mPaint = null;
			mRect = null;
		}
	}
	
	/** secondary thread to do the drawings */
	private PainterThread mThread;
	
	public PainterView(Context context, AttributeSet attributes) {
		super(context, attributes);
		
		//Retrieve the surface holder and set the callback
		SurfaceHolder surfaceHolder = getHolder();
		surfaceHolder.addCallback(this);
		
		//Creating a thread without starting it yet
		mThread = new PainterThread(surfaceHolder, null);
		mThread.setRunning(true);
		mThread.start();
	}
	
	/**
	 * getter method to return the secondary thread
	 */
	public PainterThread getThread() {
		return mThread;
	}
	
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		mThread.setSurfaceSize(width, height);
	}
	
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mThread.setPaused(false);
	}
	
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mThread.setPaused(true);
	}
}
